﻿using Ardalis.GuardClauses;
using CleanArchitecture.Core.Events;
using CleanArchitecture.Core.Handlers;
using CleanArchitecture.Core.Interfaces;
using CleanArchitecture.Core.Interfaces.Cache;
using CleanArchitecture.Infrastructure.Configuration;
using CleanArchitecture.Infrastructure.Entities;
using CleanArchitecture.Infrastructure.Entities.Business;
using CleanArchitecture.Infrastructure.Entities.Common;
using CleanArchitecture.Infrastructure.Interfaces;
using CleanArchitecture.Infrastructure.Interfaces.Business;
using Masuit.LuceneEFCore.SearchEngine.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;

namespace CleanArchitecture.Infrastructure.Services.Business
{
    /// <summary>
    /// Blog service
    /// </summary>
    public partial class BlogService : IBlogService
    {
        #region Fields
        private readonly IDomainEventDispatcher _eventDispatcher;
        private readonly IRepository<BlogItem> _blogItemRepository;
        private readonly IRepository<Column> _columnRepository;
        private readonly IRepository<AdminRoleBusColumnMapping> _rolecolumnMappingRespository;
        private readonly IStaticCacheManager _cacheManager;
        private readonly ILuceneIndexer _luceneIndexer;
        private readonly IDbContext _dbContext;
        #endregion

        #region Ctor

        public BlogService(IDomainEventDispatcher eventDispatcher,
            IRepository<BlogItem> blogItemRepository,
            IRepository<Column> columnRepository,
            IRepository<AdminRoleBusColumnMapping> rolecolumnMappingRepo,
            IStaticCacheManager cacheManager,
            ILuceneIndexer luceneIndexer,
            IDbContext dbContext)
        {
            _eventDispatcher = eventDispatcher;
            _blogItemRepository = blogItemRepository;
            _columnRepository = columnRepository;
            _rolecolumnMappingRespository = rolecolumnMappingRepo;
            _luceneIndexer = luceneIndexer;
            _cacheManager = cacheManager;
            _dbContext = dbContext;
        }

        #endregion

        #region Methods

        #region Blog

        /// <summary>
        /// Deletes a news
        /// DavidLee 2019/11/28 所有业务模型原则上不删除数据库对应数据，而是置为不可用
        /// </summary>
        /// <param name="blogItems">News item</param>
        /// <param name="logEvent">Whether to log Delete Event</param>
        public virtual void DeleteBlogsBatch(IList<BlogItem> blogItems, bool logEvent = true)
        {
            if (blogItems == null || blogItems.Count == 0)
                throw new ArgumentNullException(nameof(blogItems));

            string logMsg = $"批量删除博客记录{blogItems.Count}条，ID：";
            foreach(var blog in blogItems)
            {
                blog.Title = "Deleted-" + blog.Title;
                blog.IsActive = EntityEnumConfig.YesorNo.No;
                logMsg += blog.Id.ToString() + ",";
                _luceneIndexer.Delete(blog);
            }
            _blogItemRepository.Update(blogItems);

            //event notification
            if(logEvent)
            {
                var entity = new BlogItem();
                entity.SysLogLvl = Core.Configuration.SystemLogLevel.Information;
                entity.SysLogMsg = logMsg;
                _eventDispatcher.Dispatch(new EntityDeletedEvent<BlogItem>(entity));
            }
        }
        /// <summary>
        /// 当导航菜单为单页内容导航时，查询该导航菜单所对应的博客文章
        /// </summary>
        /// <param name="columnid"></param>
        /// <returns></returns>
        public virtual BlogItem GetBlogItemForSinglePageColumn(int columnid)
        {
            /* mysql query 
             * SELECT blog.* FROM (
             *  SELECT Id AS ColumnId
             *  FROM bus_column 
             *  WHERE Id = 15 AND ColumnType = 3 AND IsActive = 1
             *  ) AS col LEFT JOIN bus_blog AS blog ON blog.FK_ColumnId = col.ColumnId 
             *  LIMIT 1
             */
            if (columnid == 0) return null;
            var query = (from column in _columnRepository.Table
                         where column.Id == columnid && column.ColumnType == EntityEnumConfig.ColumnType.BlogColumn && column.IsDisplay == EntityEnumConfig.YesorNo.YES && column.IsActive == EntityEnumConfig.YesorNo.YES
                         join blog in _blogItemRepository.Table on column.Id equals blog.FKColumnId into tempjoin
                         from j in tempjoin.DefaultIfEmpty()
                         select j).FirstOrDefault();
            return query;
        }
        /// <summary>
        /// Gets a Blog
        /// </summary>
        /// <param name="blogId">The blog identifier</param>
        /// <returns>News</returns>
        public virtual BlogItem GetBlogItemById(int blogId)
        {
            if (blogId == 0) return null;

            return _blogItemRepository.GetById(blogId);
        }

        /// <summary>
        /// Gets news
        /// </summary>
        /// <param name="blogIds">The blog identifiers</param>
        /// <returns>News</returns>
        public virtual IList<BlogItem> GetBlogItemListByIds(int[] blogIds)
        {
            var query = _blogItemRepository.Table;
            return query.Where(p => blogIds.Contains(p.Id)).ToList();
        }

        /// <summary>
        /// Gets all blogs
        /// </summary>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <returns>News items</returns>
        public virtual IViewPagedList<BlogItem> GetBlogItemsByPage(int columnId = 0, int[] roleIds = null, int pageIndex = 0, int pageSize = int.MaxValue)
        {
            var query = _blogItemRepository.TableNoTracking;
            if (columnId > 0) query = query.Where(n => n.FKColumnId == columnId);
            query = query.Where(n => n.IsActive == EntityEnumConfig.YesorNo.YES)
                                           .Distinct().OrderByDescending(n => n.IsRecommend).ThenBy(n => n.DisplayOrder);
            //DavidLee 2020/07/31 增加按角色权限查询数据功能
            if (roleIds != null)
            {
                query = from n in query
                        join ca in _rolecolumnMappingRespository.Table
                        on n.FKColumnId equals ca.FKColumnId into templeftjoin
                        from m in templeftjoin.DefaultIfEmpty()
                        where m.IsAllow == EntityEnumConfig.YesorNo.YES && roleIds.Contains(m.FKAdminRoleId)
                        select n;
            }
            var blogs = new ViewPagedList<BlogItem>(query, pageIndex, pageSize);
            return blogs;
        }
        /// <summary>
        /// 按指定语言及博客浏览量排序统计查询最热门的前 {top} 篇博文
        /// 为提高查询效率使用MemoryCache缓存，因为对实时统计要求不高，只需要缓存期过期后更新查询
        /// </summary>
        /// <param name="langid">指定语言LanguageId</param>
        /// <param name="top">获取博文数量</param>
        /// <returns></returns>
        public virtual IList<HotArticleQuery> GetHotBlogItems(int langid, int top = 10)
        {
            Guard.Against.NegativeOrZero(langid, "LanguageId");
            return _cacheManager.Get(DefaultCacheConfig.HotBlogsCacheKey, () =>
            {
                string selectSQL = string.Empty; //多次一举只为排版好看
                selectSQL += "SELECT BlogVisitCount.EntityId AS Id, BlogVisitCount.ColumnId AS ColumnId, BlogVisitCount.Title AS Title";
                selectSQL += " FROM(";
                selectSQL += " SELECT logtbl.EntityId AS EntityId, datatbl.FK_ColumnId AS ColumnId, datatbl.Title AS Title, COUNT(logtbl.EntityId) AS VisitCount";
                selectSQL += " FROM bus_activelog AS logtbl";
                selectSQL += " JOIN (";
                selectSQL += "  SELECT Id, FK_ColumnId, Title FROM bus_blog WHERE FK_ColumnId IN (";
                selectSQL += $"    SELECT Id FROM bus_column WHERE ColumnType= {(int)EntityEnumConfig.ColumnType.BlogColumn} AND FK_LanguageId = {langid}";
                selectSQL += "    )) AS datatbl ON logtbl.EntityId = datatbl.Id";
                selectSQL += $"  WHERE logtbl.ActiveName = '{ActiveLogName.VisitBlogPage}' AND logtbl.EntityName = '{nameof(BlogItem)}'";
                selectSQL += "  GROUP BY EntityId";
                selectSQL += "  ORDER BY VisitCount DESC";
                selectSQL += $" ) AS BlogVisitCount LIMIT {top}";
                var result = _dbContext.QueryFromSql<HotArticleQuery>(selectSQL).ToList();
                return result;
            }, 1440); //按天缓存查询
        }

        public virtual int StatisticsBlogsCount()
        {
            var blogCount = (from blog in _blogItemRepository.TableNoTracking
                              where blog.IsActive == EntityEnumConfig.YesorNo.YES
                              select blog).Count();
            return blogCount;
        }
        /// <summary>
        /// Inserts a blog item
        /// </summary>
        /// <param name="blog">BlogItem</param>
        public virtual void InsertBlog(BlogItem blog)
        {
            if (blog == null)
                throw new ArgumentNullException(nameof(blog));

            _blogItemRepository.Insert(blog);
            _luceneIndexer.Add(blog); //单纯的只添加索引库
            //event notification
            if (!string.IsNullOrEmpty(blog.SysLogMsg))
                _eventDispatcher.Dispatch(new EntityInsertedEvent<BlogItem>(blog));
        }

        /// <summary>
        /// Updates the blog item
        /// </summary>
        /// <param name="news">BlogItem</param>
        public virtual void UpdateBlog(BlogItem blog)
        {
            if (blog == null)
                throw new ArgumentNullException(nameof(blog));

            _blogItemRepository.Update(blog, new string[] { nameof(blog.CreatedOnTimeUtc) });
            _luceneIndexer.Update(blog);
            //event notification
            if (!string.IsNullOrEmpty(blog.SysLogMsg))
                _eventDispatcher.Dispatch(new EntityUpdatedEvent<BlogItem>(blog));
        }

        /// <summary>
        /// Get a value indicating whether a BlogItem is available now (availability dates)
        /// </summary>
        /// <param name="blogItem">blog item</param>
        /// <param name="dateTime">Datetime to check; pass null to use current date</param>
        /// <returns>Result</returns> 
        public virtual bool IsBlogAvailable(BlogItem blogItem, DateTime? dateTime = null)
        {
            if (blogItem == null)
                throw new ArgumentNullException(nameof(blogItem));

            if (!dateTime.HasValue) dateTime = DateTime.UtcNow;
            if (blogItem.StartTaskTimeUtc.HasValue && blogItem.StartTaskTimeUtc.Value >= dateTime)
                return false;

            if (blogItem.IsActive == EntityEnumConfig.YesorNo.No)
                return false;

            return true;
        }
        #endregion

        #endregion
    }
}
